unless defined?(RediPress) && RediPress.cli?
  raise "You can't load this file. Please load the redipress/cli file instead."
end

module RediPress
  module CLI
    class Configurations < Thor
      desc "execute", "Execute a configuration against a server"
      def execute
        # Prepare
        prepare

        # Print a hello message
        prompt.ok("Hello, let's configure a server!")

        # Get the server host
        host = prompt_for_host

        # Print a new line
        new_line

        # Get the configuration
        configurations = prompt_for_configurations

        # Create a Hash to hold the options
        options = Hash.new

        # Loop over each configuration
        configurations.each do |configuration|
          # Print a new line
          new_line

          # Get the configuration's options
          options[configuration.slug] = prompt_for_configuration_options(configuration)
        end

        # Print a new line
        new_line

        # Prompt to continue
        exit(1) unless prompt.yes?("Proceed with the above settings?")

        # Loop over each configuration
        configurations.each do |configuration|
          # Print a new line
          new_line

          begin
            # Check if the configuration is compatible with the host
            compatible = configuration.compatible?(host, options)
          rescue => e
            # Print a new line
            new_line

            # Print an error message
            prompt.error("An exception was raised whilst running a configuration:")
            prompt.error("-------------------------------------------------------")

            raise e
          end

          # Check if the configuration is not compatible
          unless compatible
            # Print a warning message
            prompt.warning("'#{configuration.name}' is not compatible with #{host.username}@#{host.hostname}")

            exit 1
          end
        end

        # Print a message
        prompt.ok("The selected configurations appear to be compatible with #{host.username}@#{host.hostname}")

        # Create a Hash to hold the results
        results = Hash.new

        # Loop over each configuration
        configurations.each do |configuration|
          # Print a new line
          new_line

          # Print a message
          prompt.ok("Configuring '#{configuration.name}' ...")
          prompt.ok("------------------------------------------------------------")

          begin
            # Perform the configuration on the server
            results[configuration.slug] = configuration.configure(host, options[configuration.slug])
          rescue RediPress::ConfigurationFailed => e
            # Print a new line
            new_line

            # Print an error message
            prompt.error("Configuration '#{configuration.name}' failed:")
            prompt.error("------------------------------------------------------------")
            prompt.error(e.error)

            exit 1
          rescue => e
            # Print a new line
            new_line

            # Print an error message
            prompt.error("Configuration '#{configuration.name}' raised an error:")
            prompt.error("------------------------------------------------------------")

            raise e
          end
        end

        # Print a new line
        new_line

        # The server has been configured
        prompt.ok("Woo hoo! Your server has been configured.")

        # Loop over each configuration
        configurations.each do |configuration|
          # Get the result
          result = results[configuration.slug]

          # Go next if there aren't any results to output
          next if result.nil? || result.empty? || false == result.is_a?(Hash)

          # Print a new line
          new_line

          # Print the configuration's name
          prompt.ok(configuration.name)
          prompt.ok("------------------------------------------------------------")

          # Print a table containing the results
          puts TTY::Table.new(rows: result.map { |k, v| [" #{k} ", " #{v} "] }).render(:ascii)
        end

        nil
      end

      # Set the default task to execute
      default_task :execute

      no_tasks do
        # Prompt the user for the server host
        #
        # Example:
        #   >> prompt_for_host
        #   => #<SSHKit::Host:0x00000000000000>
        #
        def prompt_for_host
          # Print a short message to explain why we need the following information
          prompt.ok("We need the following to be able to login to your server:")

          # Prompt for the server's hostname or IP address
          hostname = prompt.ask("Hostname/IP:") do |q|
            q.required(true)
            q.validate(/^(([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$/)
          end

          # Prompt for the server's username
          username = prompt.ask("Username:") do |q|
            q.required(true)
            q.validate(/^[a-z]+$/)
          end

          # Prompt for the server's password
          password = prompt.ask("Password (leave blank to use ssh keys):") do |q|
            q.required(false)
          end

          # Create an SSHKit::Host object
          host = SSHKit::Host.new("#{username}@#{hostname}")

          # Check if a password was provided
          if password
            # Set the password on the host
            host.password = password
          else
            # Load all ssh keys
            RediPress::SSHKeys.load_ssh_keys
          end

          host
        end

        # Prompt the user for the configurations to use
        #
        # Example:
        #   >> prompt_for_configurations
        #   => [#<RediPress::Configuration::Test:0x00000000000000>]
        #
        def prompt_for_configurations
          # Print a short message to explain why we need the following information
          prompt.ok("Pick the configurations you'd like to use:")

          begin
            # Load the actual configuration classes
            configurations = RediPress::Configuration.all
          rescue => e
            prompt.error("Unable to load one or more configurations.")
            prompt.error("--------------------------------------------------")

            raise e
          end

          # Ensure there is at least one configuration available
          if configurations.nil? || configurations.empty?
            prompt.error("There aren't any configurations available to use.")
            prompt.error("--------------------------------------------------")

            raise RediPress::NoConfigurationsAvailable
          end

          # Prompt for the configurations to use
          configurations = prompt.multi_select("Configurations:", configurations.values)

          configurations
        end

        # Prompt the user for all options required by the specified configuration
        #
        # Example:
        #   >> prompt_for_configuration_options(configuration)
        #   => { "username" => "test" }
        #
        def prompt_for_configuration_options(configuration)
          return nil unless configuration.parameters && configuration.parameters.count > 0

          # Print a short message to explain why we need the following information
          prompt.ok("Options for #{configuration.name}:")

          # Create an empty hash to store the configuration's options
          options = Hash.new

          # Loop through each parameter on the configuration
          configuration.parameters.each do |parameter|
            if :text == parameter.type
              options[parameter.slug] = prompt.ask(" #{parameter.name}:") do |q|
                q.required(parameter.required)
                q.validate(parameter.validation) if parameter.validation
                q.default(parameter.default) if parameter.default
              end
            elsif :yes == parameter.type
              options[parameter.slug] = prompt.yes?(" #{parameter.name}:")
            elsif :no == parameter.type
              options[parameter.slug] = prompt.no?(" #{parameter.name}:")
            elsif :select == parameter.type
              options[parameter.slug] = prompt.select(" #{parameter.name}:", parameter.options)
            elsif :multi_select == parameter.type
              options[parameter.slug] = prompt.multi_select(" #{parameter.name}:", parameter.options)
            else
              prompt.error("'#{parameter.name}' has an unknown type of '#{parameter.type}'.")

              exit 1
            end
          end

          options
        end

        # Define taskless functionality
        no_tasks do
          # Include the helper functionality
          include RediPress::CLI::Helper
        end
      end
    end
  end
end
